package com.qianxun.plugins.log;

import com.qianxun.plugins.expression.ExpressionUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;

/**
 * log 代理对象
 * @author: huwei
 * @date: 2019/10/15 17:36
 * @version: 1.0.0
 */
public class LogProxy {

    @Pointcut(value = "@annotation(com.qianxun.plugins.log.LogPoint)")
    public void logPoint() {
    }

    @Around("logPoint()")
    public Object log(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        LogInfo logInfo = parse(proceedingJoinPoint ,null ,null);
        Logger logger = getLogger(logInfo);
        //前置日志
        logger.beforeLog(logInfo);

        Object result = null;
        try {
            //先执行业务
            result = proceedingJoinPoint.proceed();
        } catch (Exception cause) {
            //异常日志
            logInfo.setStatus(false).setCause(cause);
            logger.errorLog(logInfo);
            throw cause;
        } finally {
            logInfo.setResult(result);
            //后置日志
            logger.afterLog(logInfo);
        }
        return result;
    }

    private LogInfo parse(ProceedingJoinPoint proceedingJoinPoint ,Object result ,Throwable cause) throws NoSuchMethodException {
        LogInfo logInfo = new LogInfo();
        //执行结果
        logInfo.setResult(result);
        //方法执行失败
        if(cause != null){
            logInfo.setCause(cause).setStatus(false);
        }
        //目标类
        Object target = proceedingJoinPoint.getTarget();
        //类信息设置
        logInfo.setClassName(target.getClass().getName());
        logInfo.setSimpleClassName(target.getClass().getSimpleName());

        //目标方法
        Signature sig = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature =  (MethodSignature) sig;
        Method method = methodSignature.getMethod();
        //方法信息设置
        logInfo.setMethodName(method.getName());
        logInfo.setMethodSignature(method.toString());
        logInfo.setParameterArray(Arrays.asList(proceedingJoinPoint.getArgs()));
        //参数名和参数值的映射map
        Map<String ,Object> parameterMap = ParameterResolver.parseNameValueMap(method ,proceedingJoinPoint.getArgs() , LogParam.class);
        fillParameterMap(methodSignature.getParameterNames() ,parameterMap ,proceedingJoinPoint.getArgs());
        logInfo.setParameterMap(parameterMap);

        LogPoint logPoint = method.getDeclaredAnnotation(LogPoint.class);
        logInfo.setLogPoint(logPoint);
        logInfo.setKey(placeholderInject(logPoint.key() ,logInfo));
        logInfo.setSign(logPoint.sign());
        logInfo.setTitle(placeholderInject(logPoint.title() ,logInfo));
        logInfo.setNotes(placeholderInject(logPoint.notes() ,logInfo));

        return logInfo;
    }

    private String placeholderInject(String expressionString, LogInfo logInfo) {
        return ExpressionUtil.build().parse(expressionString, logInfo.getParameterMap());
    }

    /**
     * 填补参数map映射列表未设置到的参数
     */
    private void fillParameterMap(String[] parameterNames ,Map<String ,Object> parameterMap ,Object[] args){
        for(int i = 0 ,len = parameterNames.length ;i < len ;++i){
            if(parameterMap.get(parameterNames[i]) == null){
                parameterMap.put(parameterNames[i] ,args[i]);
            }
        }
    }

    private Logger getLogger(LogInfo logInfo){
        Class<? extends Logger> cls = logInfo.getLogPoint().logger();
        try {
            Constructor<? extends  Logger> constructor = cls.getConstructor();
            constructor.setAccessible(true);
            return constructor.newInstance();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(cls.getName().concat(" 对象必须具有无参构造方法") ,e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(cls.getName().concat(" 对象无参构造方法必须是public的") ,e);
        } catch (InstantiationException e) {
            throw new RuntimeException(cls.getName().concat(" 对象构造失败") ,e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(cls.getName().concat(" 对象构造失败") ,e);
        }
    }

}
